package org.dei.perla.sys.device.fpc;

import java.util.*;
import org.dei.perla.component.LoggableComponent;
import org.dei.perla.component.Startable;
import org.dei.perla.parser.clauses.OnUnsupportedSampleRateOptions;
import org.dei.perla.parser.expressions.NodeLogicalObjectAttribute;
import org.dei.perla.parser.statements.Statement;
import org.dei.perla.sys.query.llqruntime.datastructures.Record;
import org.dei.perla.utils.logger.LogRecord.Type;
import org.dei.perla.utils.logger.LogRecord.Verbosity;
import org.dei.perla.utils.messages.AdapterFpcMessage;
import org.dei.perla.utils.pipe.Pipe;
import org.dei.perla.utils.waiter.Waitable;
import org.dei.perla.utils.waiter.Waiter;

/**
 * Questa classe astrae un dispositivo fisico connesso al sistema.
 * Si occupa di tradurre e comunicare le richeste verso il 
 * dispositivo e le risposte ricevute.
 */
public abstract class FunctionalityProxyComponent extends LoggableComponent implements Startable {
	
	/**
	 * La struttura dati contenenente le altre strutture dati necessarie al funzionamento della classe.
	 */
	private final FPCDataStructure pFPCDataStructure;
	
	/**
	 * Pipe per dati in arrivo dal dispositivo
	 */
	private final Pipe<AdapterFpcMessage> inputPipe;
	
	/**
	 * Waiter dei dati in arrivo dal dispositivo
	 */
	private final Waiter<Pipe<AdapterFpcMessage>> inputPipeWaiter;
	
	/**
	 * Gruppo di pipe per i dati in uscita (un FPC può servire piu' DataCollectors)
	 */
	private final ArrayList<Pipe<Record>> outputPipeArrayList;
	
	
	/**
	 * Inner class che gestice la ricezione dei dati dal dispositivo astratto
	 */
	private class PipeReader implements Runnable {
		
		public void run() {
			
			Waitable<Pipe<AdapterFpcMessage>> waitable;
			Pipe<AdapterFpcMessage> pipe;
			AdapterFpcMessage message;
			
			while(true) {
				waitable = inputPipeWaiter.waitNext();
				pipe = waitable.getParentComponent();
				message = pipe.dequeue();
				
				if (message != null) {
				 	readMessageFromAdapter(message.getPayload());
				 	/**/
				} else {
					inputPipeWaiter.removeWaitable(waitable);
					putNullOnOutputPipeArrayList();
					pipe.stop();
					pipeClosedCallBack(pipe);
					break;
				}
			}
			loggerNotify(Type.Information, getName() + " terminated!", Verbosity.Low);
		}

	}
	
	/**
	 * Indica se il componente è attualmente avviato
	 */
	protected boolean started;
	
	
	/**
	 * Metodo per inizializzare l'FPC. Viene eseguito nel costruttore
	 */
	protected abstract void initDefault();
	
	
	/**
	 * Metodo che implementa il comportamento da seguire nel caso la Pipe di comunicazione con il dispositivo fisico
	 * sia stata chiusa (sia manualmente, sia in caso di errore)
	 * @param pipe
	 */
	protected abstract void pipeClosedCallBack(Pipe<AdapterFpcMessage> pipe);
	
	/**
	 * Invia null sulle pipe verso i DataCollector per segnalarne la chiusura
	 */
	synchronized final protected void putNullOnOutputPipeArrayList(){
		for(Pipe<?> p: outputPipeArrayList)
			p.enqueue(null);
	}

	/**
	 * Converte uno stream di byte in un record (interpreta)
	 * Viene invocato quando l'FPC riceve un messaggio dai livelli sottostanti
	 * */
	protected abstract void readMessageFromAdapter(byte[] payload);
	
	/**
	 * Richiede all'FPC una nuova pipe (da usare quando ci si aggancia un nuovo DataCollector)
	 */
	synchronized final public Pipe<Record> getNewOutputPipe(){
		Pipe<Record> pipe = new Pipe<Record>(this.getName() + "_PIPE_" + (outputPipeArrayList.size()+1));
		pipe.start();
		outputPipeArrayList.add(pipe);
		return pipe;
	}
	
	/**
	 * Costruttore
	 * @param parName il nome da dare a questo FPC (e.g: "FPC per i sensori tipo A")
	 * @param inputPipe la pipe per scambiare i dati con il dispotivo
	 */
	public FunctionalityProxyComponent(String parName, Pipe<AdapterFpcMessage> inputPipe, FPCDataStructure parDataStructure) {
		super(parName);		
		this.started = false;
		this.inputPipe = inputPipe;
		this.inputPipeWaiter = new Waiter<Pipe<AdapterFpcMessage>>();
		this.inputPipeWaiter.addWaitable(inputPipe.getWaitable());
		this.outputPipeArrayList = new ArrayList<Pipe<Record>>();
		this.pFPCDataStructure = parDataStructure;
		this.initDefault();
	}
	

	/**
	 * Restituisce TRUE se l'FPC è attivo e funzionante, FALSE altrimenti
	 */
	synchronized final public boolean isStarted() {
		return started;
	}


	/**
	 * Permette di avviare l'FPC
	 */
	synchronized public void start() {
		inputPipeWaiter.start();
		new Thread(new PipeReader()).start();
		started = true;
		loggerNotify(Type.Information, getName() + " has just started its activity", Verbosity.Low);
		
	}

	/**
	 * Permette di stoppare l'FPC
	 */
	synchronized public void stop() {
		inputPipe.enqueue(null);
		started = false;
		loggerNotify(Type.Information, getName() + " has just stopped its activity", Verbosity.Low);
	}

	/**
	 * Setta il tempo di sampling
	 */
	public abstract boolean setSamplingTime(long parTime, OnUnsupportedSampleRateOptions parBehaviour);
	
	/**
	 * Registra una nuova query da eseguire sull'FPC
	 * @param parAttrbiutesNames lista contenente i nomi degli attributi richiesti dalla query
	 * @return TRUE se ok, FALSE se errore
	 */
	public abstract boolean registerQuery(Statement parStatement);

	/**
	 * Quando una query termina va de-registrata dall'FPC
	 * @return TRUE se ok, FALSE altrimenti
	 */
	public abstract boolean unRegisterQuery();
	
	
	/**
	 * Restituisce l'elenco di attributi che l'FPC è in grado di campionare (ovvero, in altri termini, gli attributi che sta wrappando)
	 * @deprecated E' un metodo che va' sicuramente eliminato. Al momento viene utilizzato solo dalla prima versione del Registry di Arthur.
	 *   
	 */
	public abstract ArrayList<NodeLogicalObjectAttribute> getAttributeList();
	
	/**
	 * Normalmente il flusso di record parte dall'FPC e attraverso le pipe arriva a destinazione in modo indipendente
	 * da quest'ultima (si veda la classe pipe.java). Tuttavia e' talvolta necessario forzare il campionamento: caso tipico della
	 * clausola REFRESH.
	 *	 
	 */
	public abstract ArrayList<Record> forceSampling(); 
}
